# Safety Navigation Project - FaceRecognizer
# Recognize faces by using the Local Binary Patterns algorithm. Based on Philipp Wagner's example code.
from Tkinter import *
import os

import Image
import ImageTk

from MyDialog import *
import cv2 as cv
import numpy as np
from person.Person import *


# from multiprocessing import Process, Queue
# from Queue import Empty
class App(MyDialog):
    def __init__(self, master):  # master = reference to root window
        
        # For windows
        # self.cam = cv.VideoCapture(1)
        
        # For linux
        self.cam = cv.VideoCapture(0)

        # self.queue = Queue()
                        
        self.imgdir = ".images"
        self.face_size = (90, 90)
        self.cascadeFile = "haarcascade_frontalface_alt2.xml"
        self.modifiedImage = None
        # self.image_cache = None
               
        self.Persons = []  # FIXME: load tags here
        self.Persons.append(Person('juha', None, ['tag1', 'tag2', 'tag3']))
        
        frame = Frame(master)
        frame.pack()
        
        self.image_label = Label(master)  # label for the video frame
        self.image_label.pack()
        
        # self.p = Process(target=self.capture, args=(self.queue,))
        # print "starting webcam process..."
        # self.p.start()
        
        self.quit_button = Button(
            frame, text="quit", fg="red", command=master.destroy)
        
        # self.quit_button = Button(master=root, text='Quit',command=lambda: self.quit_(master,p))
        # self.quit_button.pack()
            
        # master.after(0, func=lambda: self.update_all(master))
            
        self.quit_button.pack(side=LEFT)
        
        self.add_button = Button(
            frame, text="add face", fg="blue", command=self.add_face(master))
        self.add_button.pack(side=LEFT)
        
        # just some test buttons, they're unnecessary now.         
        # self.hi_there = Button(frame, text="Hello", command=self.say_hi)
        # self.hi_there.pack(side=RIGHT)
        
        # self.start = Button(frame, text="start", command=self.say_hi)
        # self.start.pack(side=RIGHT)
        
        # load the cascade file.
        self.cascade = cv.CascadeClassifier(self.cascadeFile)
        if (self.cascade.empty()):
            print "no cascade!"
            sys.exit()         
        
        # build the model
        self.model = cv.createLBPHFaceRecognizer()
        # train the algorithm with the faces in the image directory
        self.images, self.labels, self.names = self.retrain(self.imgdir, self.model, self.face_size)
        print "trained:", len(self.images), "images", len(self.names), "persons"
        
        self.retrain_button = Button(
            frame, text="retrain", fg="blue", command=self.retrain(self.imgdir, self.model, self.face_size))
        self.retrain_button.pack(side=RIGHT)
        
        self.addtag_button = Button(
            frame, text="add tag", fg="black", command=self.add_tag)
        self.addtag_button.pack(side=RIGHT)
        
        self.deletetag_button = Button(
            frame, text="delete tag", fg="black", command=self.delete_tag)
        self.deletetag_button.pack(side=RIGHT)
        
    #multiprocessing image processing functions-------------------------------------
      
    # def test(queue):
    #   queue.put([42, None, 'hello'])
    
    # def capture(queue):
    #   #cam = cv.VideoCapture(1)
    #   while True:
    #      try:
    #         flag, frame=cam.read()
    #         if flag==0:
    #            break
    #         queue.put(frame)
    #         cv.waitKey(20)
    #      except:
    #         continue


    def say_hi(self):
        print "hi there, everyone!"

    def update_image(self, root):
        # frame = self.queue.get()
        ret, frame = self.cam.read()
        if ret == 0:
            print "couldn't read webcam frame!"
            return
        img = cv.cvtColor(frame, cv.COLOR_BGR2RGB)
        if (img == None):
            print "img is empty!"
            return
        # ##
        self.modifiedImage = img
        assert(self.modifiedImage != None)
        self.image_cache = self.modifiedImage
        a = Image.fromarray(self.recognize_face(img))
        # if (name != None):
        #    pass
            # load_tags(name)       
        b = ImageTk.PhotoImage(image=a)
        self.image_label.configure(image=b)
        self.image_label._image_cache = b  # avoid garbage collection
        root.update()
    
    def update_all(self, root):
        self.update_image(root)
        root.after(0, func=lambda: self.update_all(root))
    
    # def quit_(self, root, process):
    #    process.terminate()
    #   root.destroy()
        
    def recognize_face(self, img):
        # take a picture with the webcam.
        # ret, img = self.cam.read()
        
        # convert the image to grayscale.
        gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
        gray = cv.equalizeHist(gray)
        
        # try to detect a face in the img:
        rects = self.cascade.detectMultiScale(gray, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30), flags=cv.CASCADE_SCALE_IMAGE)       
        # roi = modifiedImage
        for x, y, w, h in rects:
            # crop & resize it 
            self.modifiedImage = cv.resize(gray[y:y + h, x:x + h], self.face_size)
            # give some visual feedback for the cascade detection
            cv.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0))
            if len(self.images) > 0:
                # model.predict is going to return the predicted label and
                # the associated confidence:
                [p_label, p_confidence] = self.model.predict(np.asarray(self.modifiedImage))
                name = "unknown"
                if p_label != -1:  # found a match. 
                    name = self.names[p_label]
                    # look up the tags.
                    for Person in self.Persons:
                        if Person.name == name:
                            # print "name: ",name, "tags: ",Person.tags
                            offset = 0
                            for tag in Person.tags:  # draw the tags below the rectangle
                                cv.putText(img, "%s" % (tag), (x + 50 + offset, y + h + 15), cv.FONT_HERSHEY_PLAIN, 1.3, (0, 200, 0))
                                offset += len(tag) * 20
                cv.putText(img, "%s %.2f" % (name, p_confidence), (x + 10, y + 20), cv.FONT_HERSHEY_PLAIN, 1.3, (0, 200, 0))
                
            break  # use only 1st detected

        return img
        # cv.imshow('facedetect', img)
        # cv.waitKey(100)
        
    def add_face(self, root):
        pass
        # d = MyDialog(root)
        # name = tkSimpleDialog.askstring("Name", "enter your name")
        # if name == None:
        #    return
        # assert(modifiedImage != None)
  
 
        # if (self.modifiedImage != None):
        #    #print "DEBUG: found an image."
        #    d = MyDialog(root)
        #    root.wait_window(d.top)
        #    name = d.apply()
        #    # make a folder for that person,
        #    dirname = os.path.join(imgdir,name)
        #    try:
        #        os.mkdir(dirname)
        #    except:
        #        pass # dir already exists.
        #    # save image
        #    path=os.path.join(dirname,"%d.png" %(rand.uniform(0,10000)))
        #    print "added:",path
        #    cv.imwrite(path, self.modifiedImage)
    
    # added a names list(z)
    
    def add_tag(self):
        pass
        
    def delete_tag(self):
        # dialog: read name of person, list of tags to be deleted
        pass
    
    def read_images(self, path, sz=None):
        """Reads the images in a given folder, resizes images on the fly if size is given.

            Args:
            path: Path to a folder with subfolders representing the subjects (persons).
            sz: A tuple with the size Resizes

        Returns:
            A list [X,y,z]

                X: The images, which is a Python list of numpy arrays.
                y: The corresponding labels (the unique number of the subject, person) in a Python list.
                z: A list of person-names, indexed by label
        """
        c = 0
        X, y, z = [], [], []
        for dirname, dirnames, filenames in os.walk(path):
            for subdirname in dirnames:
                subject_path = os.path.join(dirname, subdirname)
                for filename in os.listdir(subject_path):
                    try:
                        im = cv.imread(os.path.join(subject_path, filename), cv.IMREAD_GRAYSCALE)
                        if (len(im) == 0):
                            continue  # not an image                        
                        # resize to given size (if given)
                        if (sz is not None):
                            im = cv.resize(im, sz)
                        X.append(np.asarray(im, dtype=np.uint8))
                        y.append(c)
                    except IOError, (errno, strerror):
                        print "I/O error({0}): {1}".format(errno, strerror)
                    except:
                        print "Unexpected error:", sys.exc_info()[0]
                        raise
                c = c + 1
                z.append(subdirname)
        return [X, y, z]
     
    # reload the images & retrain the model.
    # note, that lbp would give you the possibility of 
    # just updating the model with additonal data instead.
    def retrain(self, imgpath, model, sz) :
        # read in the image data. This must be a valid path!
        X, y, names = self.read_images(imgpath, sz)
        if len(X) == 0:
            print "image path empty", imgpath
            return [[], [], []]
        # Learn the model. Remember our function returns Python lists,
        # so we use np.asarray to turn them into NumPy lists to make
        # the OpenCV wrapper happy:
        # Also convert labels to 32bit integers. This is a workaround for 64bit machines,
        model.train(np.asarray(X), np.asarray(y, dtype=np.int32))
        return [X, y, names]

 

if __name__ == "__main__":
    
    root = Tk()
    root.wm_title("Safety Navigation Project - FaceRecognizer")
    app = App(root)
    
    root.resizable(width=FALSE, height=FALSE)  # prevent window resizing
    root.after(0, func=lambda: app.update_all(root))
    
    root.mainloop()
    # p.join()
    # print 'image capture process exit'
    # root.destroy()
